#include <cm/file/config_file.h>
#include <cm/cm_constant.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* 配置文件中的键值对, 长度有限制 */
struct config_item {
	char key[LEN_MAX_ITEM_NAME];
	char value[LEN_MAX_ITEM_NAME];
};

/* 配置段 */
struct config_section {
	char name[LEN_MAX_SECTION_NAME];
	struct config_item items[MAX_ITEM_PER_SECTION];
};

/* 配置文件的定义，每个打开的配置文件对应一个该结构 */
struct config_file {

	int max_sections;

	struct config_section *sections;

	char name[LEN_MAX_FILE_NAME];
};

/* 在配置文件cfg中，查找段名为section,键名称为key的配置项，返回这个配置项的常指针 */
static inline const struct config_item *cfg_find_item_const(const cfg_t *cfg,
		const char *section, const char *key)
{
	const struct config_item *ret = NULL;
	int i;

	for (i = 0; i < cfg->max_sections; i++) {

		if (strcmp(cfg->sections[i].name, section) != 0)
			continue;

		const struct config_section *psec = &(cfg->sections[i]);
		int j;
		for (j = 0; j < MAX_ITEM_PER_SECTION; j++) {
			if (strlen(psec->items[j].key) > 0) {
				if (strcmp(psec->items[j].key, key) == 0) {
					ret = &(psec->items[j]);
					return ret;
				}
			} else {
				break;
			}
		}
		break;

	}

	return NULL;
}

static inline struct config_item *cfg_find_item(cfg_t *cfg, const char *section,
		const char *key)
{
	struct config_item *ret = NULL;
	int i;
	for (i = 0; i < cfg->max_sections; i++) {
		if (strcmp(cfg->sections[i].name, section) == 0) {
			struct config_section *psec = &(cfg->sections[i]);
			int j;
			for (j = 0; j < MAX_ITEM_PER_SECTION; j++) {
				if (strlen(psec->items[j].key) > 0
						&& strcmp(psec->items[j].key,
								key) == 0) {
					ret = &(psec->items[j]);

					return ret;
				}
			}
		}
	}

	return NULL;
}

static inline struct config_item *cfg_find_empty_item(cfg_t *cfg,
		const char *section)
{
	struct config_item *ret = NULL;
	int i;
	for (i = 0; i < cfg->max_sections; i++) {
		if (strcmp(cfg->sections[i].name, section) == 0) {
			struct config_section *psec = &(cfg->sections[i]);
			int j;
			for (j = 0; j < MAX_ITEM_PER_SECTION; j++) {
				if (strlen(psec->items[j].key) == 0) {
					ret = &(psec->items[j]);
					return ret;
				}
			}
		}
	}

	return NULL;
}

static inline struct config_section *cfg_find_empty_section(cfg_t *cfg)
{
	struct config_section *ret = NULL;
	int i;
	for (i = 0; i < cfg->max_sections; i++) {
		if (strlen(cfg->sections[i].name) == 0) {
			return &(cfg->sections[i]);
		}
	}
	return ret;
}

static inline struct config_section *cfg_find_section(cfg_t *cfg,
		const char *section)
{
	struct config_section *ret = NULL;
	int i;
	for (i = 0; i < cfg->max_sections; i++) {
		if (strcmp(cfg->sections[i].name, section) == 0) {
			return &(cfg->sections[i]);
		}
	}
	return ret;
}

static void cfg_output(cfg_t *file, FILE* fout)
{
	if (file == NULL)
		return;

	int i;
	for (i = 0; i < file->max_sections; i++) {
		struct config_section *psec = &(file->sections[i]);
		if (strlen(psec->name) > 0) {
			fprintf(fout, "[%s]\n", psec->name);
			int j;
			for (j = 0; j < MAX_ITEM_PER_SECTION; j++) {
				if (strlen(psec->items[j].key) > 0) {
					fprintf(fout, "%s=%s\n",
							psec->items[j].key,
							psec->items[j].value);
				} else {
					break;
				}

			}
		}
	}
}

int cfg_getvalue(const cfg_t *cfg, const char *section, const char *key,
		char *value, size_t size)
{
	if (cfg == NULL || section == NULL || key == NULL || value == NULL)
		return -1;

	const struct config_item *item = cfg_find_item_const(cfg, section, key);

	if (item == NULL) {
		return -1;
	} else {
		if (strlen(item->key) > 0 && strcmp(item->key, key) == 0) {
			strncpy(value, item->value, size);

			return 0;
		}
	}
	return -1;
}

int cfg_setvalue(cfg_t *cfg, const char *section, const char *key,
		const char *value)
{
	if (cfg == NULL || section == NULL || key == NULL || value == NULL)
		return -1;

	char sec_nospace[LEN_MAX_ITEM_NAME];
	char key_nospace[LEN_MAX_ITEM_NAME];
	char val_nospace[LEN_MAX_ITEM_NAME];
	strncpy_nospace(sec_nospace, section, LEN_MAX_ITEM_NAME);
	strncpy_nospace(key_nospace, key, LEN_MAX_ITEM_NAME);
	strncpy_nospace(val_nospace, value, LEN_MAX_ITEM_NAME);

	struct config_item *item = cfg_find_item(cfg, sec_nospace, key_nospace);
	if (item == NULL)
		return -1;
	else
		strncpy_nospace(item->value, val_nospace, LEN_MAX_ITEM_NAME);

	return 0;
}

int cfg_addvalue(cfg_t *cfg, const char *section, const char *key,
		const char *value)
{

	if (cfg == NULL || section == NULL || key == NULL || value == NULL)
		return -1;

	char sec_nospace[LEN_MAX_ITEM_NAME];
	char key_nospace[LEN_MAX_ITEM_NAME];
	char val_nospace[LEN_MAX_ITEM_NAME];
	strncpy_nospace(sec_nospace, section, LEN_MAX_ITEM_NAME);
	strncpy_nospace(key_nospace, key, LEN_MAX_ITEM_NAME);
	strncpy_nospace(val_nospace, value, LEN_MAX_ITEM_NAME);

	struct config_item *item = cfg_find_item(cfg, sec_nospace, key_nospace);
	if (item != NULL) {
		cfg_setvalue(cfg, sec_nospace, key_nospace, val_nospace);
	} else {
		if (cfg_find_section(cfg, sec_nospace) == NULL) {
			struct config_section *sec = cfg_find_empty_section(
					cfg);
			if (sec == NULL) {
				return -1;
			} else {
				strncpy_nospace(sec->name, sec_nospace,
				LEN_MAX_ITEM_NAME);
				strncpy_nospace(sec->items[0].key, key_nospace,
				LEN_MAX_ITEM_NAME);
				strncpy_nospace(sec->items[0].value,
						val_nospace, LEN_MAX_ITEM_NAME);
			}
		} else {
			struct config_item *item = cfg_find_empty_item(cfg,
					sec_nospace);
			strncpy_nospace(item->key, key_nospace,
			LEN_MAX_ITEM_NAME);
			strncpy_nospace(item->value, val_nospace,
			LEN_MAX_ITEM_NAME);
		}
	}
	return 0;
}

static void cfg_save_file(struct config_file *file)
{
	if (file == NULL)
		return;

	FILE *fout = fopen(file->name, "w");
	if (fout == NULL)
		return;

	cfg_output(file, fout);

	fclose(fout);
}

static int cfg_read_file(struct config_file *file)
{

	if (file == NULL)
		return -1;

	FILE *fin = fopen(file->name, "a+");
	if (fin == NULL)
		return -1;

	fseek(fin, 0, SEEK_SET);

	int sec_idx = -1;
	int item_idx = 0;

	size_t buf_size = 0;
	char* buffer = NULL;
	while (1) {
		int ret = getline(&buffer, &buf_size, fin);
		if (ret == -1)
			break;

		if (strlen(buffer) == 0 || strstart_with(buffer, '#', ret))
			continue;
		// New section
		if (strstart_with(buffer, '[', buf_size)) {
			char *temp = strchr(buffer, ']');
			if (temp == NULL)
				continue; // wrong config

			int pos = temp - buffer;

			sec_idx++;
			item_idx = 0;
			temp = strchr(buffer, '[');
			strncpy(file->sections[sec_idx].name, temp + 1,
					pos - (temp + 1 - buffer));
		} else if (sec_idx == -1) {
			continue; // This line is not in a section
		} else {
			char *temp = strchr(buffer, '=');
			if (temp == NULL) {
				continue;
			} else {
				int pos = temp - buffer;
				strncpy_nospace(
						file->sections[sec_idx].items[item_idx].key,
						buffer, pos);
				strncpy_nospace(
						file->sections[sec_idx].items[item_idx].value,
						temp + 1, ret);

				item_idx++;
			}
		}
	} // End of while

	if (buffer != NULL)
		free(buffer);
	fclose(fin);
	return 0;
}

void cfg_printf(cfg_t *file)
{
	printf("\n\n");
	printf("*** content of configuration file: %s ***\n", file->name);
	cfg_output(file, stdout);
	printf("\n\n");
}
/*
 * 打开一个配置文件，返回一个配置文件描述符
 * 函数内部为配置项的存储分配了空间，不使用的描述符应当手动关闭
 * 分配空间失败或者大小超出限制则返回NULL
 */
cfg_t* cfg_open(const char *path, int size)
{

	if (size <= 0 || size > MAX_SECTION_PER_FILE)
		return NULL;

	cfg_t *ptr = (cfg_t *) calloc(1, sizeof(cfg_t));

	if (ptr == NULL)
		return NULL;

	ptr->max_sections = size;
	ptr->sections = (struct config_section *) calloc(1,
			sizeof(struct config_section) * size);

	if (ptr->sections == NULL) {
		free(ptr);
		return NULL;
	}

	strncpy(ptr->name, path, sizeof(ptr->name));

	if (cfg_read_file(ptr) != 0) {
		cfg_close(&ptr);
		return NULL;
	}

	return ptr;
}

void cfg_close(cfg_t **cfg)
{
	if (cfg == NULL)
		return;

	if (*cfg == NULL)
		return;

	if ((*cfg)->sections != NULL)
		free((*cfg)->sections);

	free(*cfg);

	*cfg = NULL;
}

void cfg_flush(cfg_t *cfg)
{
	cfg_save_file(cfg);
}
